/*
 * Copyright 2012-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hup.utils.commons.start;

import lombok.extern.log4j.Log4j2;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.boot.system.ApplicationPid;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.net.InetAddress;
import java.util.concurrent.Callable;


/**
 * org.springframework.boot.StartupInfoLogger
 * 使用前必须在main方法中调用 {@link StartupInfo#init()}
 */
@Log4j2
public class StartupInfo {

    private static final Class<?> MAIN_CLZ = deduceMainApplicationClass();

    /**
     * @return main函数所在的类
     */
    private static Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

    /**
     * 触发初始化App信息,必须在main方法启动时调用才有效
     */
    public static void init() {
        if (MAIN_CLZ == null) {
            log.warn("找不到主类");
        }
    }

    public static String getAllMessage() {
        StringBuilder sb = new StringBuilder();
        sb.append("Starting ");
        sb.append(getApplicationName());

        String version = getVersion();
        if (version != null) {
            sb.append(' ').append(version);
        }

        sb.append(" on ").append(getPcName());
        sb.append(" with PID ").append(getPid());
        sb.append(" (").append(getAppPath());
        sb.append(" started by ").append(getBootUserName());
        sb.append(" in ").append(getBootCommandDir()).append(")");
        return sb.toString();
    }

    /**
     * 想处理jar使用管理员运行时[./]目录定位到system32的问题;
     * 最终解决方案: app不特殊处理,在计划任务的[输入jar路径的操作]页面中,输入[起始于]的信息即可!
     */
    @Deprecated
    public static void handleJarBootLocation() {
        File jarFile = getJarFile();
        if (jarFile == null) {
            return;//不是以jar文件运行的,暂不处理
        }
        File bootCommandDir = new File(getBootCommandDir());
        if (!jarFile.getParentFile().equals(bootCommandDir)) {
            log.warn("启动时路径不在程序所在文件夹中!, jar={}, bootCommandDir={}, ./={}", jarFile, bootCommandDir, new File("./").getAbsolutePath());
            log.info("./logs={}", new File("./logs").getAbsoluteFile().getAbsolutePath());
            //测试命令 java -jar G:\proJava\WinLoginTask\target\WinLoginTask-V1.0.2_RC0.jar
            System.setProperty("user.dir", jarFile.getParent());
            log.info("重置user.dir={}, ./={}", getBootCommandDir(), new File("./").getAbsolutePath());
            log.info("./logs={}", new File("./logs").getAbsoluteFile().getAbsolutePath());
        }
    }

    /**
     * @return main函数所在类的类名
     */
    public static String getApplicationName() {
        return (MAIN_CLZ != null) ? ClassUtils.getShortName(MAIN_CLZ)
                : "unknownAppName";
    }

    /**
     * @return 程序中文名, 需要在{@link #MAIN_CLZ}中加入 {@link AppInfo},省缺时等同于 {@link #getApplicationChName()}
     */
    public static String getApplicationChName() {
        if (MAIN_CLZ == null) {
            return getApplicationName();
        }
        AppInfo appInfo = MAIN_CLZ.getAnnotation(AppInfo.class);
        if (appInfo == null) {
            return getApplicationName();
        } else {
            return appInfo.chName();
        }
    }

    /**
     * 获取程序版本号
     *
     * @return 开发时-空字符串;其他情况-maven.pom下的版本号
     * @apiNote 使用 {@link VersionConfig#version}可以解决返回空字符串
     */
    public static String getVersion() {
        return getValue(() -> MAIN_CLZ != null ? MAIN_CLZ.getPackage().getImplementationVersion() : null);
    }

    public static String getPcName() {
        return getValue(() -> InetAddress.getLocalHost().getHostName());
    }

    /**
     * @return 启动app的计算机用户名
     */
    public static String getBootUserName() {
        return System.getProperty("user.name");
    }

    /**
     * 进程pid
     */
    public static String getPid() {
        return getValue(() -> new ApplicationPid().toString());
    }

    /**
     * 获取启动程序时,[控制台/终端/命令行]所在的路径
     *
     * @return 一般jar执行时-jar所在文件夹;开发时-项目文件夹;管理员运行且未特殊处理时-windows\system32
     */
    public static String getBootCommandDir() {
        return System.getProperty("user.dir");
    }

    /**
     * 获取程序所在的路径
     *
     * @return jar执行时-jar所在的文件夹路径;开发时-编译文件夹路径(一般是target\classes);
     */
    public static String getAppPath() {
        ApplicationHome home = new ApplicationHome(MAIN_CLZ);
        if (home.getSource() == null) {
            throw new IllegalArgumentException("ApplicationHome构造异常?");
        }
        if (home.getSource().getAbsolutePath().endsWith(".jar")) {
            return home.getSource().getParentFile().getAbsolutePath();
        }
        return home.getSource().getAbsolutePath();
    }

    /**
     * @return jar文件启动时-jar文件的路径;其他情况-null
     */
    public static File getJarFile() {
        ApplicationHome home = new ApplicationHome(MAIN_CLZ);
        if (home.getSource() == null) {
            throw new IllegalArgumentException("ApplicationHome构造异常?");
        }
        if (home.getSource().getAbsolutePath().endsWith(".jar")) {
            return home.getSource();
        }
        return null;
    }

    private static String getValue(Callable<Object> call) {
        try {
            Object value = call.call();
            if (value != null && StringUtils.hasLength(value.toString())) {
                return String.valueOf(value);
            }
        } catch (Exception ex) {
            // do nothing
        }
        return null;
    }

}
